﻿using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Guide;
using KuiHuaBaoDian.Services.Asset;
using Newtonsoft.Json;

namespace KuiHuaBaoDian.Services.Guide {

    public interface IGuideService {

        UniTask Trigger(IGuideTriggerKey key);

        void Recovery(IGuideTriggerKey key);

        IGuideSection GetSectionByIndex(int index);

        IGuideSection GetSectionByID(int id);

        void Dump();
    }

    public static class GuideService {

        /// <summary>
        /// 单例
        /// </summary>
        public static IGuideService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<IGuideService>();
        private static IGuideService m_Instance;
    }

    public abstract class KHBD_GuideService : IGuideService, IGuideServiceInternal, IDisposable {

        private readonly IReadOnlyList<IGuideSection> EMPTY_SECTION_LIST = (new List<IGuideSection>()).AsReadOnly();

        private class AssetRequest : IAssetRequest {

            public string Key { get; set; }

            public CancellationToken CancellationToken { get; } = CancellationToken.None;

            public IAssetDownloadProgress DownloadProgress { get; } = null;

            public bool IgnoreFailureLog => false;
        }

        private GuideSetting m_Setting;

        private Dictionary<IGuideTriggerKey, List<GuideSection>> m_TriggerSectionListDictionary;

        protected IReadOnlyList<IGuideSection> SectionList { get; private set; }
        protected IReadOnlyDictionary<int, IGuideSection> SectionDictionary { get; private set; }

        async UniTask IGuideServiceInternal.Initialize() {
            m_TriggerSectionListDictionary = new();
            var asset = await AssetService.Instance.Load<GuideSetting>(new AssetRequest { Key = nameof(GuideSetting) });
            m_Setting = UnityEngine.Object.Instantiate(asset.Content);
            if (!m_Setting.IsEnabled) {
                SectionList = EMPTY_SECTION_LIST;
                return;
            }
            var list = new List<GuideSection>();
            var dictionary = new Dictionary<int, IGuideSection>();
            foreach (var serializedSection in m_Setting.SectionList) {
                var section = UnityEngine.Object.Instantiate(serializedSection);
                section.Initialize();
                foreach (var triggerEntity in section.TriggerEntities) {
                    var key = triggerEntity.Key;
                    if (key != null) {
                        if (!m_TriggerSectionListDictionary.TryGetValue(key, out var sectionList)) {
                            sectionList = m_TriggerSectionListDictionary[key] = new List<GuideSection>();
                        }
                        sectionList.Add(section);
                    }
                }
                dictionary.Add(section.ID, section);
                list.Add(section);
            }
            SectionList = list.AsReadOnly();
            SectionDictionary = dictionary;
        }

        void IDisposable.Dispose() {
            SectionList = null;
            m_TriggerSectionListDictionary = null;
            SectionDictionary = null;
        }

        public async UniTask Trigger(IGuideTriggerKey key) {
            if (m_TriggerSectionListDictionary == null) {
                return;
            }
            if (m_TriggerSectionListDictionary.TryGetValue(key, out var sectionList)) {
                foreach (var section in sectionList) {
                    if (!section.IsDone && section.Trigger(key)) {
                        await OnTrigger(section);
                        await Execute(section);
                        break;
                    }
                }
            }
        }

        public void Recovery(IGuideTriggerKey key) {
            if (m_TriggerSectionListDictionary == null) {
                return;
            }
            if (m_TriggerSectionListDictionary.TryGetValue(key, out var sectionList)) {
                foreach (var section in sectionList) {
                    if (!section.IsDone) {
                        section.Recovery(key);
                    }
                }
            }
        }

        protected abstract UniTask OnTrigger(IGuideSection section);

        private async UniTask Execute(GuideSection section) {
            var requireSubmitData = false;
            var isFailure = false;
            IReadOnlyList<GuideReference> referenceList = null;
            foreach (var step in section.Steps) {
                if (step is IGuideStepStart) {
                    referenceList = null;
                }
                if (step is GuideStep_GetVariable getVariableStep) {
                    referenceList = section.GetVariable(getVariableStep.Key);
                }
                await OnStep(step);
                var stepInternal = step as IGuideStepInternal;
                await stepInternal.Execute(section, referenceList);
                if (step is IGuideQueryInternal query) {
                    referenceList = query.ReferenceList;
                }
                if (step is GuideStep_SetVariable setVariableStep) {
                    section.SetVariable(setVariableStep.Key, referenceList);
                }
                if (step is GuideCommand_RequireSubmitData) {
                    requireSubmitData = true;
                }
                if (step is GuideCommand_CurrentSectionFailure) {
                    isFailure = true;
                }
                stepInternal.Dispose();
            }

            if (isFailure) {
                requireSubmitData = false;
            } else {
                section.Done();
            }
            await OnFinished(section, requireSubmitData);
        }

        protected abstract UniTask OnStep(IGuideStep step);

        protected abstract UniTask OnFinished(IGuideSection section, bool requireSubmitData);

        protected string GetData() {
            var data = JsonConvert.SerializeObject(SectionList);
            return data;
        }

        void IGuideServiceInternal.SetData(string data) {
            if (string.IsNullOrWhiteSpace(data)) {
                return;
            }
            var list = JsonConvert.DeserializeObject<List<GuideSectionData>>(data);
            if (list != null) {
                foreach (var item in list) {
                    if (item != GuideSectionData.Empty) {
                        if (SectionDictionary.TryGetValue(item.ID, out var section)) {
                            (section as IGuideSectionInternal).SetDataFrom(item);
                        }
                    }
                }
            }
        }

        void IGuideServiceInternal.StartWait() => StartWait();
        protected virtual void StartWait() { }

        void IGuideServiceInternal.EndWait() => EndWait();
        protected virtual void EndWait() { }

        public IGuideSection GetSectionByIndex(int index) {
            if (SectionList != null && SectionList.Count > index && index > -1) {
                return SectionList[index];
            }
            return null;
        }

        public IGuideSection GetSectionByID(int id) {
            if (SectionDictionary != null && SectionDictionary.TryGetValue(id, out var result)) {
                return result;
            }
            return null;
        }

        public void Dump() {
            if (SectionList == null) {
                return;
            }
            foreach (var section in SectionList) {
                UnityEngine.Debug.Log($"{section.Name}:{section.IsDone}");
            }
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Guide {

    public interface IGuideServiceInternal {

        UniTask Initialize();

        void SetData(string data);

        void StartWait();

        void EndWait();
    }
}
